//
// Created by 陈成 on 2019/9/19.
//

#include "InstI.h"

InstI::InstI(uint32_t pc, sc_bv<32> value, RegBank<REG_NUM> *r, RegBank<CSR_NUM> *csr, Mem *m) :
        Inst(pc, value, r, csr, m),
        imm   (value.range(31, 20)),
        rs1   (value.range(19, 15)),
        funct3(value.range(14, 12)),
        rd    (value.range(11,  7)),
        opcode(value.range( 6,  0)) {

}

uint32_t
InstI::exec() {
    uint32_t pc_nxt = pc +4;
    switch (opcode.to_uint()) {
    case 0b0000011:
        handleType0();
        break;
    case 0b0000111:
        handleType1();
        break;
    case 0b0011011:
        pc_nxt = handleType2();
        break;
    case 0b0100011:
        handleType3();
        break;
    case 0b0101011: // FENCE
        // do noting
        break;
    case 0b0101111:
        handleType4();
        break;
    case 0b0110011: // ECALL & EBREAK
        // do noting
        break;
    default:
        assert(false);
        break;
    }
    return pc_nxt;
}

void
InstI::handleType0() {
    uint32_t u32_rd  = rd.to_uint();
    uint32_t u32_rs1 = r->read(rs1.to_uint()).to_uint();
    int32_t  i32_rs1 = r->read(rs1.to_uint()).to_int();
    uint32_t u32_imm = extendImm(imm).to_uint();
    int32_t  i32_imm = extendImm(imm).to_int();

    uint32_t rslt;
    switch (funct3.to_uint()) {
    case 0b000: // ADDI
        if (rs1.to_uint() == 0 && rd.to_uint() == 0 && imm.to_uint() == 0) {
            LOG("NOP");
            // NOP
        } else {
            rslt = u32_rs1 + u32_imm;
            r->write(u32_rd, rslt);

            LOG("ADDI RD: %d, RS: %d, IMM: %d", rd.to_uint(), rs1.to_uint(), imm.to_uint());
            LOG(" - %d + %d = %d", u32_rs1, u32_imm, rslt);
        }
        break;
    case 0b001: // SLTI
        rslt = (i32_rs1 < i32_imm ? 1 : 0);
        r->write(u32_rd, rslt);

        LOG("SLTI RD: %d, RS: %d, IMM: %d", rd.to_uint(), rs1.to_uint(), imm.to_uint());
        LOG(" - %d < %d = %d", u32_rs1, u32_imm, rslt);
        break;
    case 0b010: // SLTU
        rslt = (u32_rs1 < u32_imm ? 1 : 0);
        r->write(u32_rd, rslt);

        LOG("SLT RD: %d, RS: %d, IMM: %d", rd.to_uint(), rs1.to_uint(), imm.to_uint());
        LOG(" - %d < %d = %d", u32_rs1, u32_imm, rslt);
        break;
    case 0b011: // ANDI
        rslt = (u32_rs1 & u32_imm);
        r->write(u32_rd, rslt);

        LOG("ANDI RD: %d, RS: %d, IMM: %d", rd.to_uint(), rs1.to_uint(), imm.to_uint());
        LOG(" - %d & %d = %d", u32_rs1, u32_imm, rslt);
        break;
    case 0b100: // ORI
        rslt = (u32_rs1 | u32_imm);
        r->write(u32_rd, rslt);

        LOG("ORI RD: %d, RS: %d, IMM: %d", rd.to_uint(), rs1.to_uint(), imm.to_uint());
        LOG(" - %d | %d = %d", u32_rs1, u32_imm, rslt);
        break;
    case 0b101: // XORI
        rslt = (u32_rs1 ^ u32_imm);
        r->write(u32_rd, rslt);

        LOG("ORI RD: %d, RS: %d, IMM: %d", rd.to_uint(), rs1.to_uint(), imm.to_uint());
        LOG(" - %d ^ %d = %d", u32_rs1, u32_imm, rslt);
        break;
    default:
        assert(false);
        break;
    }
}

void
InstI::handleType1() {
    uint32_t u32_rd  = rd.to_uint();
    uint32_t u32_rs1 = r->read(rs1.to_uint()).to_uint();
    uint32_t shift_num = imm.range(4, 0).to_uint();

    uint32_t rslt;
    switch ((imm.range(11, 5), funct3).to_uint()) {
    case 0b0000000000: // SLLI
        rslt = u32_rs1 << shift_num;
        r->write(u32_rd, rslt);

        LOG("SLLI RD: %d, RS: %d, IMM: %d", rd.to_uint(), rs1.to_uint(), imm.to_uint());
        LOG(" - %d << %d = %d", u32_rs1, shift_num, rslt);
        break;
    case 0b0000000001: // SRLI
        rslt = u32_rs1 >> shift_num;
        r->write(u32_rd, rslt);

        LOG("SRLI RD: %d, RS: %d, IMM: %d", rd.to_uint(), rs1.to_uint(), imm.to_uint());
        LOG(" - %d >> %d = %d", u32_rs1, shift_num, rslt);
        break;
    case 0b0100000001: // SRAI
        rslt = u32_rs1 >> shift_num;
        rslt = rslt | (u32_rs1 & 0x80000000);
        r->write(u32_rd, rslt);

        LOG("SRAI RD: %d, RS: %d, IMM: %d", rd.to_uint(), rs1.to_uint(), imm.to_uint());
        LOG(" - %d >> %d = %d", u32_rs1, shift_num, rslt);
        break;
    default:
        assert(false);
    }
}

uint32_t
InstI::handleType2() { // JALR
    uint32_t u32_rs1 = r->read(rs1.to_uint()).to_uint();
    int32_t  i32_imm = extendImm(imm).to_int();
    uint32_t  pc_nxt = u32_rs1 + i32_imm;

    r->write(rd.to_uint(), pc + 4);

    LOG("SRAI RD: %d, RS: %d, IMM: %d", rd.to_uint(), rs1.to_uint(), imm.to_uint());
    LOG(" - PC: %d + %d = %d", u32_rs1, i32_imm, pc_nxt);

    return pc_nxt;
}

void
InstI::handleType3() {
    uint32_t u32_rs1 = r->read(rs1.to_uint()).to_uint();
    int32_t  i32_imm = imm.to_int();

    sc_bv<32> b32_mem = m->read4Bytes(u32_rs1 + i32_imm);

    switch (funct3.to_uint()) {
    case 0b000: // LW
        r->write(rd.to_uint(), b32_mem.to_uint());

        LOG("LW RD: %d, RS: %d, IMM: %d", rd.to_uint(), rs1.to_uint(), imm.to_uint());
        LOG(" - addr: %d + %d, data: %d", u32_rs1, i32_imm, b32_mem.to_uint());
        break;
    case 0b001: // LH
        r->write(rd.to_uint(), b32_mem.range(15, 0).to_int());
        break;
    case 0b010: // LHU
        r->write(rd.to_uint(), b32_mem.range(15, 0).to_uint());
        break;
    case 0b011: // LB
        r->write(rd.to_uint(), b32_mem.range(7, 0).to_int());
        break;
    case 0b100: // LBU
        r->write(rd.to_uint(), b32_mem.range(7, 0).to_uint());
        break;
    default:
        assert(false);
        break;
    }
}

void InstI::handleType4() {
    uint32_t u32_csr = csr->read(imm.to_uint()).to_uint();
    uint32_t u32_rs1 = r->read(rs1.to_uint()).to_uint();

    switch (funct3.to_uint()) {
    case 0b000: // CSRRW
        if (rd.to_uint() != 0) {
            r->write(rd.to_uint(), u32_csr);
        }
        csr->write(imm.to_uint(), u32_rs1);
        break;
    case 0b001: // CSRRS
        if (rs1.to_uint() != 0) {
            csr->write(imm.to_uint(), u32_rs1 | u32_csr);
        }
        r->write(rd.to_uint(), u32_csr);
        break;
    case 0b010: // CSRRC
        if (rs1.to_uint() != 0) {
            csr->write(imm.to_uint(), u32_rs1 & (~u32_csr));
        }
        r->write(rd.to_uint(), u32_csr);
        break;
    case 0b011: // CSRRWI
        if (rd.to_uint() != 0) {
            r->write(rd.to_uint(), u32_csr);
        }
        csr->write(imm.to_uint(), rs1.to_uint());
        break;
    case 0b100: // CSRRSI
        if (rs1.to_uint() != 0) {
            csr->write(imm.to_uint(), rs1.to_uint() | u32_csr);
        }
        r->write(rd.to_uint(), u32_csr);
        break;
    case 0b101: // CSRRCI
        if (rs1.to_uint() != 0) {
            csr->write(imm.to_uint(), rs1.to_uint() & (~u32_csr));
        }
        r->write(rd.to_uint(), u32_csr);
        break;
    default:
        assert(false);
        break;
    }
}
